#ifndef UDPSERVER_HPP
#define UDPSERVER_HPP

#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>

#include <cstring>
#include <functional>
#include <iostream>
#include <memory>
#include <string>

#include "log.hpp"  // 引用日志插件

#define BUF_SIZE 1024

using Comfunc_t = std::function<std::string(const std::string &)>;
using Echofunc_t = std::function<std::string(const std::string &,
                                             const std::string &, uint16_t)>;

Log log_;

enum { SOCK_CREATE_FAIL = 1, SOCK_BIND_FAIL };

class UdpServer {
 public:
  UdpServer(const uint16_t port = defaultport)
      /* const std::string &ip = defaultip*/
      : sockfd_(0), port_(port), /*ip_(ip),*/ isrunning_(false) {}

  ~UdpServer() {}

  void Init() {
    /*
      1. 创建 UDP socket
    */
    sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);  // 表示使用IPv4 UDP协议
    // SOCK_DGRAM 表示允许一个面向数据报 无连接 不可靠的数据传输

    if (sockfd_ < 0) {
      log_(FATAL, "socket create fail , the errornum : %d\n",
           sockfd_);  // 打印日志信息确认套接字创建是否成功

      exit(SOCK_CREATE_FAIL);  // 套接字创建失败时退出
    }
    log_(INFO, "socket create sucess , sockfd : %d", sockfd_);

    /*
      2. bind 绑定
    */

    // 2.1 类似于设置阻塞信号集 此处只是设置了一个变量
    struct sockaddr_in localsr;
    bzero(&localsr, sizeof(localsr));
    localsr.sin_family = AF_INET;     // 表明结构体地址类型
    localsr.sin_port = htons(port_);  // 表明需要绑定的端口
    // localsr.sin_addr.s_addr = inet_addr(ip_.c_str());  //
    // 表明需要绑定的IP地址
    localsr.sin_addr.s_addr = INADDR_ANY;
    socklen_t locallen = sizeof(localsr);
    // bind 地址为0时表示可收到来自所有主机的数据 是一种比较推荐的做法

    /*
      其中端口号和IP地址必须是网络字节序的
      (IP与端口必定是客户端和服务端互相发送的)
      使用 htons 用于 uint16_t 的转网络字节序
      sin_addr是一个结构体 该结构体的s_addr成员才是需要填入的IP
      使用 inet_addr 用于 const char* 类型的IP 转 uint16_t(网络字节序)
    */

    // 2.2 进行 bind
    if (bind(sockfd_, (const struct sockaddr *)&localsr, locallen) < 0) {
      log_(FATAL, "socket bind fail, err string :%s", strerror(errno));
      exit(SOCK_BIND_FAIL);
    }
    // bind 成功
    log_(INFO, "socket bind sucess , sockfd : %d", sockfd_);
  }

  /* void Run(func_t fun) {
    // 服务器需要一直运行
    isrunning_ = true;
    char inbuf[BUF_SIZE] = {0};
    while (isrunning_) {
      struct sockaddr_in client;
      socklen_t len = sizeof(client);
      bzero(&client, sizeof(client));

      // 使用 recvfrom 接收来自客户端发送的消息
      size_t n = recvfrom(sockfd_, inbuf, sizeof(inbuf) - 1, 0,
                          (struct sockaddr *)&client, &len);
      // client 与 len 保存着客户端发送过来的套接字信息

      if (n < 0) {
        log_(WARNING, "recvfrom fail, err string :%s", strerror(errno));
        continue;
      }
      log_(INFO, "recvfrom sucess");

      inbuf[n] = 0;  // 当字符串进行打印

      // 充当数据处理
      std::string info = inbuf;  // 组合字符串
      std::string echo_string = fun(inbuf);
      std::cout << echo_string << std::endl;

      sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0,
             (const struct sockaddr *)&client, len);
      // 将数据发回给客户端
    }
  }
  */

  //////////////////////////////////////

  void Run(Echofunc_t EchoHandler) {
    // 服务器需要一直运行
    isrunning_ = true;
    char inbuf[BUF_SIZE] = {0};
    while (isrunning_) {
      struct sockaddr_in client;
      socklen_t len = sizeof(client);
      bzero(&client, sizeof(client));

      // 使用 recvfrom 接收来自客户端发送的消息
      size_t n = recvfrom(sockfd_, inbuf, sizeof(inbuf) - 1, 0,
                          (struct sockaddr *)&client, &len);
      // client 与 len 保存着客户端发送过来的套接字信息

      if (n < 0) {
        log_(WARNING, "recvfrom fail, err string :%s", strerror(errno));
        continue;
      }
      log_(INFO, "recvfrom sucess");

      uint16_t port = ntohs(client.sin_port);
      std::string ip = inet_ntoa(client.sin_addr);

      inbuf[n] = 0;  // 当字符串进行打印

      // 充当数据处理
      std::string info = inbuf;  // 组合字符串
      std::string echo_string = EchoHandler(inbuf,ip,port);
      std::cout << echo_string << std::endl;

      sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0,
             (const struct sockaddr *)&client, len);
      // 将数据发回给客户端
    }
  }

 private:
  int sockfd_;     // 套接字文件描述符
  uint16_t port_;  // 服务器进程端口号
  // std::string ip_;  // IP 地址
  bool isrunning_;  // 表明服务器的运行状态

  // static const std::string defaultip;  // 设置 ip 初始值
  static const uint16_t defaultport;  // 设置端口初始值
};

/*
  为静态的默认IP与端口设置初始值 (定义)
*/
// const std::string UdpServer::defaultip = "0.0.0.0";
const uint16_t UdpServer::defaultport = 8080;

#endif